/******************************************************************************************
 * Import
 ******************************************************************************************/
import _ from 'lodash';
import Collect from './colelct';

/******************************************************************************************
 * Export Class
 ******************************************************************************************/
/**
 * 请求设置对象结构：
 * {
 *      url: string,                    // url地址,
 *      data: object,                   // 参数键值对,
 *      success(data)=> {},             // 成功回调函数
 *      error(err)=> {}                 // 逻辑错误回调函数
 *      config: {                       // 设置对象
 *          indicator: {                // 指示器（吐司菜单）对象（发送http请求时显示，完成http请求时隐藏）
 *              start() {},             // 开始执行动作回调
 *              end() {}                // 结束执行动作回调
 *          },
 *          fail: {                     // 失败对象，对服务端错误、网络错误的统一处理
 *              status(res, obj) {},    // 服务器状态错误处理器
 *              parse(text, obj) {},    // 返回值解析错误处理器
 *              format(text, obj) {},   // 返回值的格式错误处理器
 *              network(err) {}         // 网络错误处理器
 *          }
 *      } 
 * }
 */
export default class Http {

    /*------------------------------------------------------------------------------------
	 * 外部调用接口
	 *-----------------------------------------------------------------------------------*/
    /**
     * get请求，参数请看_request方法
     * 
     * @param {*} obj 访问请求设置对象 
     */
    static get(obj) {
        obj['method'] = 'GET';
        this.request(obj);
    }

    /**
     * post请求，参数请看_request方法
     * 
     * @param {*} obj 访问请求设置对象 
     */
    static post(obj) {
        obj['method'] = 'POST';
        this.request(obj);
    }

    /*------------------------------------------------------------------------------------
	 * 真实请求方法
	 *-----------------------------------------------------------------------------------*/
    /**
     * 真实访问方法
     * 
     * @param {*} method 访问方法
     * @param {*} obj 访问请求设置对象 
     */
    static request(obj) {

        // ------------------------------------------------------ 
        // 显示指示器
        this._indicatorStart(obj);
  
        // ------------------------------------------------------ 
        // 设置url（主要用于get）
        if (obj.method == 'GET') {
            obj.url = obj.url + this._paramGet(obj.url, obj.data);
        }
        
        // ------------------------------------------------------ 
        // 设置body（主要用于post）
        // 注：如果不需要添加body字段，则Param.post函数返回的是null
        if (obj.method == 'POST') {
            let result = this._paramPost(obj);
            if (result != null) {
                obj['body'] = result;
            }
        }

        // ------------------------------------------------------ 
        // 根据obj设置config。config是真正需要带入request的配置
        let config = { method: obj.method };

        // ------------------------------------------------------ 
        // 设置config的body参数
        if (obj.body) config['body'] = obj.body;

        // ------------------------------------------------------ 
        // 设置config的header参数
        config['headers'] = this.getHeaders();

        // ------------------------------------------------------ 
        // 发起请求，并返回数据
        // 会进行追踪，并判断是否存在服务器状态码错误，最后将文本类型的返回值交给下一步
        fetch(obj.url, config).then((res)=> {

            // 在debug模式下，输出response信息
            console.log('--------- ' + obj.method + ' : ' + obj.url);
            if (_.isObject(obj.data)) {
                console.log(obj.data);
            }
            console.log(res);
            console.log('--------- END');

            // 返回码判断，如果不等于200，则：收集错误，调用错误回调，并直接返回
            if (parseInt(res.status) != 200) {
                Collect.tatusFail(res, obj);
                if (_.has(obj, 'config.fail.status')) {
                    obj.config.fail.status(res, obj);
                }
                return null;
            }

            // 将数据返回给下一步，注意，这里全部返回为text类型
            return res.text(); 
        })

        // ------------------------------------------------------ 
        // 数据处理
        .then((text)=> {

            // 空值判断，返回码不为200时，text为null，则直接返回
            if (text == null) {
                console.log('null')
                return;
            }
            
            // 赋值parse类型
            this.parse(text, obj);

            // 隐藏指示器
            this._indicatorEnd(obj);
        })

        // ------------------------------------------------------ 
        // 错误抓取
        .catch((err) => {

            // 收集错误，并调用错误回调
            Collect.networkFail(err, obj);
            if (_.has(obj, 'config.fail.network')) {
                obj.config.fail.network(err, obj);
            }

            // 隐藏指示器
            this._indicatorEnd(obj);
        });
    }

    /**
     * 注意：获取header信息（子类需要重写）
     */
    static getHeaders() {}

    /**
     * 注意：解析返回的字符串（子类需要重写）
     * 
     * @param {String} text 从服务器返回的原始字符串
     * @param {Object} obj 设置对象
     */
    static parse(text, obj) {}

    /*------------------------------------------------------------------------------------
	 * 参数处理方法
	 *-----------------------------------------------------------------------------------*/
    /**
     * 设置Get方法的参数
     * 
     * @param {String} url 此次访问的url
     * @param {Object} data 数据对象
     * @return {String} 需要连接到url之后的querystring
     */
    static _paramGet(url, data) {

        // 如果不是平面对象，则直接返回空串
        if (!_.isPlainObject(data)) {
            return '';
        }

        // 计算params数组
        let params = [];
        for (let key in data) {
            params.push(key + '=' + data[key]);
        }

        // 计算得到连接符号和查询字符串
        const symbol = url.indexOf('?') === -1 ? '?' : '&';
        const query = params.join('&');

        // 返回连接在url之后需要连接的参数字符串
        return symbol + query;
    }

    /**
     * 设置Post方法的参数
     * 
     * @param {Object} data 数据对象
     * @return {FormData} 如果存在data，则返回新的formData数据，否则返回null
     */
    static _paramPost(data) {

        // 如果不是平面对象，则直接返回false（代表不需要在obj添加body）
        if (!_.isPlainObject(data)) {
            return null;
        }

        // 制作formData对象，并返回
        let formData = new FormData();
        for (let key in data) {
            formData.append(key, data[key])
        }

        // 返回formData数据（代表需要在obj添加body）
        return formData;
    }

    /*------------------------------------------------------------------------------------
	 * 指示器方法
	 *-----------------------------------------------------------------------------------*/
    /**
     * 如果带有指示器，则显示指示器
     * 注意start和end都必须是方法
     * 
     * @param {Object} obj 设置对象
     */
    static _indicatorStart(obj) {
        if (_.has(obj, 'config.indicator.start')) {
            obj.config.indicator.start();
        }
    }

    /**
     * 如果带有指示器，则隐藏指示器
     * 注意start和end都必须是方法
     * 
     * @param {Object} obj 设置对象
     */
    static _indicatorEnd(obj) {
        if (_.has(obj, 'config.indicator.end')) {
            obj.config.indicator.end();
        }
    }
}
